namespace Hack.Decompiling.PEA.PE
{
  using System;
  using System.IO;
  using System.Diagnostics;
  using System.Collections.Generic;
  using System.Text;
  using System.Runtime;
  using System.Runtime.InteropServices;
  /// <summary>
  /// Machine type in FileHeader
  /// </summary>
  public enum MachineType : ushort
  {
    UNKNOWN = 0,
    I386 = 0x014c,        // Intel 386.
    R3000 = 0x0162,       // MIPS little-endian, 0x160 big-endian
    R4000 = 0x0166,       // MIPS little-endian
    R10000 = 0x0168,      // MIPS little-endian
    WCEMIPSV2 = 0x0169,   // MIPS little-endian WCE v2
    ALPHA = 0x0184,       // Alpha_AXP
    SH3 = 0x01a2,         // SH3 little-endian
    SH3DSP = 0x01a3,
    SH3E = 0x01a4,        // SH3E little-endian
    SH4 = 0x01a6,         // SH4 little-endian
    SH5 = 0x01a8,         // SH5
    ARM = 0x01c0,         // ARM Little-Endian
    THUMB = 0x01c2,
    AM33 = 0x01d3,
    POWERPC = 0x01F0,     // IBM PowerPC Little-Endian
    POWERPCFP = 0x01f1,
    IA64 = 0x0200,        // Intel 64
    MIPS16 = 0x0266,      // MIPS
    ALPHA64 = 0x0284,     // ALPHA64
    MIPSFPU = 0x0366,     // MIPS
    MIPSFPU16 = 0x0466,   // MIPS
    AXP64 = ALPHA64,
    TRICORE = 0x0520,     // Infineon
    CEF = 0x0CEF,
    EBC = 0x0EBC,         // EFI Byte Code
    AMD64 = 0x8664,       // AMD64 (K8)
    M32R = 0x9041,        // M32R little-endian
    CEE = 0xC0EE,
  }

  [Flags]
  public enum FileAttributes:ushort
  {
    RELOCS_STRIPPED         = 0x0001,  // Relocation info stripped from file.
    EXECUTABLE_IMAGE        = 0x0002,  // File is executable  (i.e. no unresolved externel references).
    LINE_NUMS_STRIPPED      = 0x0004,  // Line nunbers stripped from file.
    LOCAL_SYMS_STRIPPED     = 0x0008,  // Local symbols stripped from file.
    AGGRESIVE_WS_TRIM       = 0x0010,  // Agressively trim working set
    LARGE_ADDRESS_AWARE     = 0x0020,  // App can handle >2gb addresses
    BYTES_REVERSED_LO       = 0x0080,  // Bytes of machine word are reversed.
    BIT32_MACHINE           = 0x0100,  // 32 bit word machine.
    DEBUG_STRIPPED          = 0x0200,  // Debugging info stripped from file in .DBG file
    REMOVABLE_RUN_FROM_SWAP = 0x0400,  // If Image is on removable media, copy and run from the swap file.
    NET_RUN_FROM_SWAP       = 0x0800,  // If Image is on Net, copy and run from the swap file.
    SYSTEM                  = 0x1000,  // System File.
    DLL                     = 0x2000,  // File is a DLL.
    UP_SYSTEM_ONLY          = 0x4000,  // File should only be run on a UP machine
    BYTES_REVERSED_HI       = 0x8000,  // Bytes of machine word are reversed.
  }

  /// <summary>
  /// File Header
  /// </summary>
  public unsafe struct COFFHeader         //20 (14H) Bytes
  {
    #region Consts
    public const int FileHeaderSize = 20;
    public const UInt32 DefaultSymbolPointer = 0x00000000;
    public const UInt32 DefaultSymbolCount  = 0x00000000;
    #endregion
    #region Statics
    public static COFFHeader it()
    {
      return new COFFHeader();
    }
    public static COFFHeader define(OptionalHeader optionalHeader, SectionHeader[] sections, FileAttributes attributes)
    {
      return it().Define(optionalHeader, sections, attributes);
    }
    public static COFFHeader define(OptionalHeader optionalHeader, SectionHeader[] sections, FileAttributes attributes, DateTime createTime)
    {
      return it().Define(optionalHeader, sections, attributes, createTime);
    }
    public static COFFHeader define(OptionalHeader optionalHeader, SectionHeader[] sections, FileAttributes attributes, DateTime createTime, MachineType machineType)
    {
      return it().Define(optionalHeader, sections, attributes, createTime, machineType);
    }
    #endregion
    #region Files
    ///<summary>
    /// The architecture type of the computer. This member can be one of the following values. 
    ///</summary>
    public UInt16 Machine;
    ///<summary>
    /// The number of sections. 
    ///</summary>
    public UInt16 NumberOfSections;
    ///<summary>
    /// The time stamp of the image. 
    /// This represents the date and time the image was created by the linker. 
    /// The value is represented in the number of seconds elapsed since midnight (00:00:00),
    /// January 1, 1970, Universal Coordinated Time, according to the system clock. 
    ///</summary>
    public UInt32 TimeDateStamp;
    ///<summary>
    /// The offset of the symbol table, in bytes, or zero if no symbol table exists. 
    ///</summary>
    public UInt32 PointerToSymbolTable;
    ///<summary>
    /// The number of symbols in the symbol table. 
    ///</summary>
    public UInt32 NumberOfSymbols;     
    /// <summary>
    /// The size of the optional header, in bytes. 
    /// </summary>
    public UInt16 SizeOfOptionalHeader;
    /// <summary>
    /// The characteristics of the image. 
    /// This member can be one or more of the following values.
    /// </summary>
    public UInt16 Characteristics;
    #endregion
    #region Optional Fields
    public OptionalHeader  _PEHeader;
    public SectionHeader[] _Sections;
    #endregion
    #region Properties
    public MachineType MachineType
    {
      get
      {
        return (MachineType)this.Machine;
      }
      set
      {
        this.Machine = (UInt16)value;
      }
    }
    public DateTime CreateTime
    {
      get
      {
        return Utils.GetDateTime((Int32)this.TimeDateStamp);
      }
      set
      {
        this.TimeDateStamp = (UInt32)Utils.GetTimeStamp(value);
      }
    }
    public FileAttributes Attributes
    {
      get
      {
        return (FileAttributes)this.Characteristics;
      }
      set
      {
        this.Characteristics = (UInt16)value;
      }
    }
    public SectionHeader[] Sections
    {
      get
      {
        return this._Sections;
      }
      set
      {
        this._Sections = value;
        this.NumberOfSections = value == null ? (UInt16)0 : (UInt16)value.Length;
      }
    }
    public OptionalHeader PEHeader
    {
      get
      {
        return this._PEHeader;
      }
    }
    public bool DebugInfoStripped
    {
      get
      {
        return (this.Attributes & FileAttributes.DEBUG_STRIPPED) != 0;
      }
      set
      {
        if (value)
        {
          this.Attributes |= FileAttributes.DEBUG_STRIPPED;
        }
        else
        {
          this.Attributes &= ~FileAttributes.DEBUG_STRIPPED;
        }
      }
    }
    public bool LocalSymbolStripped
    {
      get
      {
        return (this.Attributes & FileAttributes.LOCAL_SYMS_STRIPPED) != 0;
      }
      set
      {
        if (value)
        {
          this.Attributes |= FileAttributes.LOCAL_SYMS_STRIPPED;
        }
        else
        {
          this.Attributes &= ~FileAttributes.LOCAL_SYMS_STRIPPED;
        }
      }
    }
    public bool RelocationStripped
    {
      get
      {
        return (this.Attributes & FileAttributes.RELOCS_STRIPPED) != 0;
      }
      set
      {
        if (value)
        {
          this.Attributes |= FileAttributes.RELOCS_STRIPPED;
        }
        else
        {
          this.Attributes &= ~FileAttributes.RELOCS_STRIPPED;
        }
      }
    }
    public bool LineNumberStripped
    {
      get
      {
        return (this.Attributes & FileAttributes.LINE_NUMS_STRIPPED) != 0;
      }
      set
      {
        if (value)
        {
          this.Attributes |= FileAttributes.LINE_NUMS_STRIPPED;
        }
        else
        {
          this.Attributes &= ~FileAttributes.LINE_NUMS_STRIPPED;
        }
      }
    }
    public bool IsUniprocessorOnly
    {
      get
      {
        return (this.Attributes & FileAttributes.UP_SYSTEM_ONLY) != 0;
      }
      set
      {
        if (value)
        {
          this.Attributes |= FileAttributes.UP_SYSTEM_ONLY;
        }
        else
        {
          this.Attributes &= ~FileAttributes.UP_SYSTEM_ONLY;
        }
      }
    }
    public bool IsDLL
    {
      get
      {
        return (this.Attributes & FileAttributes.DLL) != 0;
      }
      set
      {
        if (value)
        {
          this.Attributes |= FileAttributes.DLL;
        }
        else
        {
          this.Attributes &= ~FileAttributes.DLL;
        }
      }
    }
    public bool IsExecutable
    {
      get
      {
        return (this.Attributes & FileAttributes.EXECUTABLE_IMAGE) != 0;
      }
      set
      {
        if (value)
        {
          this.Attributes |= FileAttributes.EXECUTABLE_IMAGE;
        }
        else
        {
          this.Attributes &= ~FileAttributes.EXECUTABLE_IMAGE;
        }
      }
    }
    public bool IsSystemFile
    {
      get
      {
        return (this.Attributes & FileAttributes.SYSTEM) != 0;
      }
      set
      {
        if (value)
        {
          this.Attributes |= FileAttributes.SYSTEM;
        }
        else
        {
          this.Attributes &= ~FileAttributes.SYSTEM;
        }
      }
    }
    public bool Is32BitMachine
    {
      get
      {
        return (this.Attributes & FileAttributes.BIT32_MACHINE) != 0;
      }
      set
      {
        if (value)
        {
          this.Attributes |= FileAttributes.BIT32_MACHINE;
        }
        else
        {
          this.Attributes &= ~FileAttributes.BIT32_MACHINE;
        }
      }
    }
    public bool IsAggresiveWorkingSet
    {
      get
      {
        return (this.Attributes & FileAttributes.AGGRESIVE_WS_TRIM) != 0;
      }
      set
      {
        if (value)
        {
          this.Attributes |= FileAttributes.AGGRESIVE_WS_TRIM;
        }
        else
        {
          this.Attributes &= ~FileAttributes.AGGRESIVE_WS_TRIM;
        }
      }
    }
    public bool IsLargeAddressAware
    {
      get
      {
        return (this.Attributes & FileAttributes.LARGE_ADDRESS_AWARE) != 0;
      }
      set
      {
        if (value)
        {
          this.Attributes |= FileAttributes.LARGE_ADDRESS_AWARE;
        }
        else
        {
          this.Attributes &= ~FileAttributes.LARGE_ADDRESS_AWARE;
        }
      }
    }
    public bool IsOnRemovable
    {
      get
      {
        return (this.Attributes & FileAttributes.REMOVABLE_RUN_FROM_SWAP) != 0;
      }
      set
      {
        if (value)
        {
          this.Attributes |= FileAttributes.REMOVABLE_RUN_FROM_SWAP;
        }
        else
        {
          this.Attributes &= ~FileAttributes.REMOVABLE_RUN_FROM_SWAP;
        }
      }
    }
    public bool IsOnNet
    {
      get
      {
        return (this.Attributes & FileAttributes.NET_RUN_FROM_SWAP) != 0;
      }
      set
      {
        if (value)
        {
          this.Attributes |= FileAttributes.NET_RUN_FROM_SWAP;
        }
        else
        {
          this.Attributes &= ~FileAttributes.NET_RUN_FROM_SWAP;
        }
      }
    }
    public bool IsHighByteReserved
    {
      get
      {
        return (this.Attributes & FileAttributes.BYTES_REVERSED_HI) != 0;
      }
      set
      {
        if (value)
        {
          this.Attributes |= FileAttributes.BYTES_REVERSED_HI;
        }
        else
        {
          this.Attributes &= ~FileAttributes.BYTES_REVERSED_HI;
        }
      }
    }
    public bool IsLowByteReserved
    {
      get
      {
        return (this.Attributes & FileAttributes.BYTES_REVERSED_LO) != 0;
      }
      set
      {
        if (value)
        {
          this.Attributes |= FileAttributes.BYTES_REVERSED_LO;
        }
        else
        {
          this.Attributes &= ~FileAttributes.BYTES_REVERSED_LO;
        }
      }
    }
    public bool HasOptionalHeader
    {
      get
      {
        return this.SizeOfOptionalHeader > 0;
      }
    }
    public bool HasSections
    {
      get
      {
        return this.NumberOfSections > 0;
      }
    }
    public bool HasSymbols
    {
      get
      {
        return this.NumberOfSymbols > 0;
      }
    }
    #endregion
    #region Methods
    public bool Read(BinaryReader reader)
    {
      bool valid = false;
      if (reader != null)
      {
        this.Machine              = Utils.Read16(reader);
        this.NumberOfSections     = Utils.Read16(reader);
        this.TimeDateStamp        = Utils.Read32(reader);
        this.PointerToSymbolTable = Utils.Read32(reader);
        this.NumberOfSymbols      = Utils.Read32(reader);
        this.SizeOfOptionalHeader = Utils.Read16(reader);
        this.Characteristics      = Utils.Read16(reader);

        valid = true;

        if (this.HasOptionalHeader)
        {
          Int64 current = Utils.GetPosition(reader);

          UInt16 magic = Utils.Read16(reader);

          if (Utils.SetPosition(reader, current))
          {
            if (magic == (UInt16)PEType.PE32)
            {
              this._PEHeader = OptionalHeader32.from(reader);
            }
            else if (magic == (UInt16)PEType.PE64)
            {
              this._PEHeader = OptionalHeader64.from(reader);
            }
            else
            {
              //Unrecognized optional header.
              valid = false;
            }
          }
        }
        if (valid)
        {
          if (this.HasSections)
          {
            this.ReadSections(reader);
          }
        }
      }
      return valid;
    }
    public void Write(BinaryWriter writer)
    {
      if (writer != null)
      {
        Utils.Write16(writer,this.Machine);
        Utils.Write16(writer,this.NumberOfSections);
        Utils.Write32(writer,this.TimeDateStamp);
        Utils.Write32(writer,this.PointerToSymbolTable);
        Utils.Write32(writer,this.NumberOfSymbols);
        Utils.Write32(writer,this.SizeOfOptionalHeader);
        Utils.Write32(writer,this.Characteristics);

        if (this.HasOptionalHeader)
        {
          this._PEHeader.Write(writer);
        }

        if (this.HasSections)
        {
          this.WriteSections(writer);
        }
      }
    }
    public COFFHeader Define(OptionalHeader optionalHeader, SectionHeader[] sections, FileAttributes attributes)
    {
      return this.Define(optionalHeader, sections, attributes, DateTime.Now);
    }
    public COFFHeader Define(OptionalHeader optionalHeader, SectionHeader[] sections, FileAttributes attributes, DateTime createTime)
    {
      return this.Define(optionalHeader, sections, attributes, createTime,MachineType.I386);
    }
    public COFFHeader Define(OptionalHeader optionalHeader, SectionHeader[] sections, FileAttributes attributes, DateTime createTime,MachineType machineType)
    {
      this.MachineType = machineType;
      this.CreateTime = createTime;
      this.Attributes = attributes;

      this.SetOptionalHeader(optionalHeader);

      this.SetSections(sections);

      this.PointerToSymbolTable = DefaultSymbolPointer;
      this.NumberOfSymbols = DefaultSymbolCount;

      return this;
    }
    public override string ToString()
    {
      StringBuilder builder = new StringBuilder();
      builder.AppendFormat("Machine:              {0:X4}\r\n", this.Machine);
      builder.AppendFormat("NumberOfSections      {0:X4}\r\n", this.NumberOfSections);
      builder.AppendFormat("TimeDateStamp         {0:X8}\r\n", this.TimeDateStamp);
      builder.AppendFormat("PointerToSymbolTable: {0:X8}\r\n", this.PointerToSymbolTable);
      builder.AppendFormat("NumberOfSymbols:      {0:X8}\r\n", this.NumberOfSymbols);
      builder.AppendFormat("SizeOfOptionalHeader: {0:X4}\r\n", this.SizeOfOptionalHeader);
      builder.AppendFormat("Characteristics:      {0:X4}\r\n", this.Characteristics);

      builder.AppendFormat("MachineType:          {0}\r\n", this.MachineType);
      builder.AppendFormat("CreateTime:           {0}\r\n", this.CreateTime);
      builder.AppendFormat("Attributes:           {0}\r\n", this.Attributes);
      
      if (this.HasOptionalHeader)
      {
        builder.Append(this._PEHeader.ToString());
      }
      if (this.HasSections)
      {
        foreach (SectionHeader section in this._Sections)
        {
          builder.Append(section.ToString());
        }
      }
      return builder.ToString();
    }

    #region Helpers
    internal SectionHeader[] SetSections(SectionHeader[] sections)
    {
      this.NumberOfSections = sections != null ? (UInt16)sections.Length : (UInt16)0;

      return this._Sections = sections;
    }

    internal OptionalHeader SetOptionalHeader(OptionalHeader optionalHeader)
    {
      this.SizeOfOptionalHeader = optionalHeader != null ? optionalHeader.HeaderSize :(UInt16) 0;

      return this._PEHeader = optionalHeader;
    }

    internal void ReadSections(BinaryReader reader)
    {
      this.ReadSections(reader, this.NumberOfSections);
    }
    internal void ReadSections(BinaryReader reader, UInt32 n)
    {
      this._Sections = new SectionHeader[n];

      if (reader != null)
      {
        for (UInt32 i = 0; i < n; i++)
        {
          this._Sections[(Int32)i] = SectionHeader.from(reader);
        }
      }
    }
    private void WriteSections(BinaryWriter writer)
    {
      if (writer != null && this._Sections != null)
      {
        for (UInt32 i = 0; i < (UInt32)_Sections.Length; i++)
        {
          this._Sections[(Int32)i].Write(writer);
        }
      }
    }
    #endregion
    #endregion
  }
}
